#include "flash.h"

/*
 * 查找擦除次数最少的页（用于写入）
 * 如果页的擦除计数为0xFFFFFFFF，表示该页没有擦除过，视为空白页，优先考虑这些页。
 */
uint16_t FindBestPage()
{
    uint16_t best_page = 0;
    uint32_t min_erase_count = 0xFFFFFFFF;  // 初始时设置为最大值，表示未擦除的页

    for (uint16_t page = 0; page < EEPROM_PAGE_COUNT; page++)
    {
        uint32_t addr = EEPROM_START_ADDRESS + page * PAGE_SIZE;
        uint32_t erase_count = *(__IO uint32_t*)addr;

        // 如果页的擦除计数为0xFFFFFFFF，表示该页没有擦除过
        if (erase_count == 0xFFFFFFFF)
        {
            // 如果没有找到更好的页，选择这个空白页
            best_page = page;
            break;
        }

        // 查找擦除次数最少的页
        if (erase_count < min_erase_count)
        {
            min_erase_count = erase_count;
            best_page = page;
        }
    }

    return best_page;
}


/*
 * 查找最新数据所在页（用于读取）
 */
uint16_t FindValidPage()
{
    for (uint16_t page = 0; page < EEPROM_PAGE_COUNT; page++)
    {
        uint32_t addr = EEPROM_START_ADDRESS + page * PAGE_SIZE + DATA_VALID_FLAG_OFFSET;
        if (*(__IO uint32_t*)addr == 0xFFFFFFFF)
        {
            return page;
        }
    }
    return 0xFFFF; // 未找到有效数据
}

/*
 * 读取结构体数据
 */
void EEPROM_ReadStruct(uint16_t offset, void* data, uint16_t size)
{
    uint16_t page = FindValidPage();
    if (page == 0xFFFF) return; // 无有效数据

    uint32_t addr = EEPROM_START_ADDRESS + page * PAGE_SIZE + DATA_OFFSET + offset;
    uint8_t *pData = (uint8_t*)data;

    for (uint16_t i = 0; i < size; i++)
    {
        pData[i] = *(__IO uint8_t*)(addr + i);
    }
}

/**
 * @brief  将结构体数据写入 Flash 页，并实现磨损均衡。
 *         本函数首先选择一个适合的 Flash 页（通过磨损均衡算法），然后将数据写入该页。
 *         若需要，函数还会在写入前擦除页，并在写入后更新擦除计数。
 * @param  offset:     写入数据的偏移地址（单位：字节）。该偏移地址是相对于当前页（Flash 页基地址）而言的。
 *                     如果页的大小是 1KB，`offset = 0` 表示从该页的开始地址写入数据，`offset = 512` 表示从该页的中间位置开始写入。
 * @param  data:       指向结构体数据的指针。该参数指向要写入的结构体数据。数据将会按字节写入 Flash 页。
 * @param  size:       结构体数据的大小（单位：字节）。即要写入 Flash 的数据的字节数。通常使用 `sizeof` 来计算结构体的大小。
 * @param  enableErase: 是否在写入之前擦除目标 Flash 页。
 *                      - `0`: 不擦除目标页，直接写入数据。
 *                      - `1`: 擦除目标页后写入数据。
 *                      擦除操作有助于清空旧数据，避免与新数据发生冲突，但会影响 Flash 页的擦写次数。
 * 
 * @retval flash_status_type: 返回 Flash 操作的状态。
 *                            - `FLASH_OPERATE_DONE`: 操作成功完成。
 *                            - 其他状态码：表示不同的错误状态，具体的错误码可以通过查阅 Flash 底层驱动文档来分析。
 */
flash_status_type EEPROM_WriteStruct(uint16_t offset, const void* data, uint16_t size, uint8_t enableErase)
{
    // 1. 选择一个合适的页面来存储数据（通过磨损均衡算法）
    uint16_t page = FindBestPage();  // 获取最适合写入的 Flash 页，基于磨损均衡
    uint32_t addr = EEPROM_START_ADDRESS + page * PAGE_SIZE + DATA_OFFSET + offset;  // 计算目标地址
    flash_status_type status = FLASH_OPERATE_DONE;  // 默认写入操作成功
    const uint8_t *pData = (const uint8_t*)data;  // 将结构体数据转为字节数据以便逐字节写入

    // 2. 检查数据大小是否超过当前页的可用空间
    if ((offset + size) > (PAGE_SIZE - PAGE_HEADER_SIZE)) return FLASH_PRGMERR_FLAG; // 如果数据大小超出页面范围，返回错误

    // 3. 解锁 Flash 写入权限
    flash_unlock();
    flash_flag_clear(FLASH_ODF_FLAG | FLASH_PRGMERR_FLAG | FLASH_EPPERR_FLAG);  // 清除相关标志位，确保 Flash 处于可写状态

    // 4. 如果需要擦除页，则进行擦除操作
    if (enableErase)
    {
        // 读取当前页的擦除计数，并加 1
        uint32_t erase_count = *(__IO uint32_t*)(EEPROM_START_ADDRESS + page * PAGE_SIZE) + 1;

        // 擦除当前页
        if (flash_sector_erase(EEPROM_START_ADDRESS + page * PAGE_SIZE) != FLASH_OPERATE_DONE)
        {
            flash_lock();  // 锁定 Flash
            return FLASH_PRGMERR_FLAG;  // 擦除失败，返回错误
        }

        // 重新写入擦除计数到页的指定位置
        flash_word_program(EEPROM_START_ADDRESS + page * PAGE_SIZE + ERASE_COUNTER_OFFSET, erase_count);
    }

    // 5. 标记当前页为有效数据页
    flash_word_program(EEPROM_START_ADDRESS + page * PAGE_SIZE + DATA_VALID_FLAG_OFFSET, 0xFFFFFFFF);  // 写入标记位，表示该页包含有效数据

    // 6. 写入结构体数据到 Flash 页
    for (uint16_t i = 0; i < size; i++)
    {
        status = flash_byte_program(addr + i, pData[i]);  // 按字节写入数据
        if (status != FLASH_OPERATE_DONE) break;  // 如果写入失败，跳出循环
    }

    // 7. 锁定 Flash 写入权限
    flash_lock();

    // 8. 返回 Flash 写入状态
    return status;
}


/*
 * 从Flash读取数据到RAM
 */
void EEProm_To_RAM(uint16_t Page, uint16_t Len, uint16_t* Buffer)
{
    uint16_t i;
    for (i = 0; i < Len; i++)
    {
        Buffer[i] = *(__IO uint16_t*)(EEPROM_START_ADDRESS + Page * PAGE_SIZE + i * 2);
    }
}

/*
 * 读取单个32位数据
 */
uint32_t EEProm_To_RAM_One(uint16_t Page, uint16_t index)
{
    return *(__IO uint32_t*)(EEPROM_START_ADDRESS + Page * PAGE_SIZE + index * 4);
}

/*
 * 将RAM数据写入Flash
 */
void RAM_To_EEProm(uint16_t Page, uint16_t Len, uint16_t* Buffer)
{
    uint16_t i;
    flash_status_type status;

    flash_unlock();
    flash_flag_clear(FLASH_ODF_FLAG | FLASH_PRGMERR_FLAG | FLASH_EPPERR_FLAG);

    status = flash_sector_erase(EEPROM_START_ADDRESS + Page * PAGE_SIZE);
    if (status != FLASH_OPERATE_DONE)
        return;

    for (i = 0; i < Len; i++)
    {
        status = flash_halfword_program(
            EEPROM_START_ADDRESS + Page * PAGE_SIZE + i * 2, 
            Buffer[i]
        );
        if (status != FLASH_OPERATE_DONE)
            break;
    }

    flash_lock();
}

/*
 * 写入单个32位数据
 */
void RAM_To_EEProm_One(uint16_t Page, uint16_t index, uint32_t Data, uint8_t EnableErase)
{
    flash_status_type status;

    flash_unlock();
    flash_flag_clear(FLASH_ODF_FLAG | FLASH_PRGMERR_FLAG | FLASH_EPPERR_FLAG);

    if (EnableErase == 1)
    {
        status = flash_sector_erase(EEPROM_START_ADDRESS + Page * PAGE_SIZE);
        if (status != FLASH_OPERATE_DONE)
            return;
    }

    status = flash_word_program(
        EEPROM_START_ADDRESS + Page * PAGE_SIZE + index * 4, 
        Data
    );

    flash_lock();
}
